//  IDVI 1.0 source copyright 1996 Garth A. Dickie
//
//  This source is free for non-commercial use.  No warranty, etc.
//  Please acknowledge reuse by including the line:
//
//  "Based in part on IDVI 1.0 source copyright 1996 Garth A. Dickie"
//
//  in your documentation and source code.  For commercial use or
//  distribution, please contact the author.  Please also send
//  questions, comments, bug reports, or fixes.
//
//  Best Regards,
//  Garth A. Dickie
//  dickie@elastic.avid.com

package ibook.v10.parameter;

import java.net.*;
import java.util.Hashtable;
import java.util.Vector;

import ibook.v10.colors.Colors;

public class ParameterProcessor {
    private ParameterStub   stub;
    private URL             base;

    public ParameterProcessor( ParameterStub stub, URL base ) {
        this.stub = stub;
        this.base = base;
    }

    public ParameterProcessor( ParameterStub stub ) {
        this( stub, null );
    }

    public ParameterProcessor( ) {
        this( null, null );
    }

    public ParameterStub getStub( ) {
        return stub;
    }

    public void setStub( ParameterStub stub ) {
        this.stub = stub;
    }




    public static int guessNumber( URL url, String prefix, int defaultValue ) {
        int result = defaultValue;

        String nameString = url.getFile( );
        int baseStart = nameString.lastIndexOf( '/' ) + 1;  // ??? is this portable?
        if( nameString.regionMatches( true, baseStart, prefix, 0, prefix.length( ))) {
            int numberStart = baseStart + prefix.length( );
            int numberEnd;
            int length = nameString.length( );
            for( numberEnd = numberStart; numberEnd < length; ++ numberEnd ) {
                char c = nameString.charAt( numberEnd );
                if( ! ( c == '-' || Character.isDigit( c ))) break;
            }

            if( numberEnd != numberStart )
                try {
                    result = Integer.parseInt( nameString.substring( numberStart, numberEnd ));
                } catch( NumberFormatException e ) {
                }
        }

        return result;
    }

    final private static String kSeparatorCharacters = ".-_";

    public static String guessIndex( String prefix, String defaultValue ) {
        String result = defaultValue;

        if( prefix != null ) {
            int prefixLength = prefix.length( );
            if( prefixLength > 0 ) {
                if( kSeparatorCharacters.indexOf( prefix.charAt( prefixLength - 1 )) != -1 )
                    prefixLength --;

                if( prefixLength > 0 )
                    result = prefix.substring( 0, prefixLength ) + ".html";
            }
        }

        return result;
    }

    public boolean existsParameter( String name ) {
        return stub.getParameter( name ) != null;
    }




    public String getString( String name )
            throws ParameterMissingException {

        String result = stub.getParameter( name );

        if( result == null )
            throw new ParameterMissingException( name );

        return result;
    }

    public String getString( String name, String defaultValue ) {
        String result = stub.getParameter( name );
        return result == null ? defaultValue : result;
    }

    public URL getURL( String name )
            throws ParameterMissingException, ParameterValueException {

        return getURLInternal( name, null );
    }

    public URL getURL( String name, URL defaultValue )
            throws ParameterValueException {

        URL result;

        try {
            result = getURLInternal( name, null );
        } catch( ParameterMissingException e ) {
            result = defaultValue;
        }

        return result;
    }

    public URL getURL( String name, String defaultValue )
            throws ParameterValueException {

        URL result;

        try {
            result = getURLInternal( name, defaultValue );
        } catch( ParameterMissingException e ) {
            result = null;
        }

        return result;
    }

    public URL getFileURL( String name )
            throws ParameterMissingException, ParameterValueException {

        return getFileURLInternal( name, null );
    }

    public URL getFileURL( String name, URL defaultValue )
            throws ParameterValueException {

        URL result;

        try {
            result = getFileURLInternal( name, null );
        } catch( ParameterMissingException e ) {
            result = defaultValue;
        }

        return result;
    }

    public URL getFileURL( String name, String defaultValue )
            throws ParameterValueException {

        URL result;

        try {
            result = getFileURLInternal( name, defaultValue );
        } catch( ParameterMissingException e ) {
            result = null;
        }

        return result;
    }

    public int getInteger( String name, int min, int max )
            throws ParameterMissingException, ParameterValueException {

        int result = getInteger( name );

        if( result < min )
            result = min;
        
        if( min <= max && max < result )
            result = max;

        return result;
    }

    public int getInteger( String name, int min, int max, int defaultValue )
            throws ParameterValueException {

        int result = getInteger( name, defaultValue );
        
        if( result < min )
            result = min;
        
        if( min <= max && max < result )
            result = max;
        
        return result;
    }

    public int getInteger( String name )
            throws ParameterMissingException, ParameterValueException {

        int result;
        String parameterString = stub.getParameter( name );

        if( parameterString != null )
            try {
                result = Integer.parseInt( parameterString );
            } catch( NumberFormatException e ) {
                throw new ParameterValueException( name, parameterString, "integer" );
            }
        else
            throw new ParameterMissingException( name );

        return result;
    }

    public int getInteger( String name, int defaultValue )
            throws ParameterValueException {

        int result;

        try {
            result = getInteger( name );
        } catch( ParameterMissingException e ) {
            result = defaultValue;
        }

        return result;
    }

    public int getLength( String name, int dpi, double defaultConversion )
            throws ParameterMissingException, ParameterValueException {

        int result;

        String parameterString = stub.getParameter( name );
        if( parameterString != null )
            try {
                result = parseLength( parameterString, dpi, defaultConversion );
            } catch( NumberFormatException e ) {
                throw new ParameterValueException( name, parameterString, "length" );
            }
        else
            throw new ParameterMissingException( name );
        
        return result;
    }

    public int getLength( String name, int dpi, double defaultConversion, int defaultValue )
            throws ParameterValueException {

        int result;

        try {
            result = getLength( name, dpi, defaultConversion );
        } catch( ParameterMissingException e ) {
            result = defaultValue;
        }

        return result;
    }

    public boolean getBoolean( String name )
            throws ParameterMissingException, ParameterValueException {

        boolean result;
        String parameterString = stub.getParameter( name );

        if( parameterString != null )
            if( "false".equals( parameterString ))
                result = false;
            else if( "true".equals( parameterString ))
                result = true;
            else
                throw new ParameterValueException( name, parameterString, "boolean" );
        else
            throw new ParameterMissingException( name );

        return result;
    }

    public boolean getBoolean( String name, boolean defaultValue )
            throws ParameterValueException {

        boolean result;

        try {
            result = getBoolean( name );
        } catch( ParameterMissingException e ) {
            result = defaultValue;
        }

        return result;
    }

    public int getColor( String name )
            throws ParameterMissingException, ParameterValueException {
        
        int result;
        String parameterString = getString( name );

        if( Colors.existsNamedColor( parameterString )) {
            result = Colors.getNamedColor( parameterString );
        } else if( parameterString.length( ) >= 1 && parameterString.charAt( 0 ) == '#' ) {
            try {
                result = Integer.parseInt( parameterString.substring( 1 ), 16 );
            } catch( NumberFormatException e ) {
                throw new ParameterValueException( name, parameterString, "color RRGGBB tuple" );
            }
        } else
            throw new ParameterValueException( name, parameterString, "color name" );
        
        return result;
    }

    public int getColor( String name, int defaultValue )
            throws ParameterValueException {
        
        int result;

        try {
            result = getColor( name );
        } catch( ParameterMissingException e ) {
            result = defaultValue;
        }

        return result;
    }

    public Hashtable getHashtable( String name )
            throws ParameterMissingException, ParameterValueException {

        return getHashtableInternal( name, null );
    }

    public Hashtable getHashtable( String name, Hashtable defaultValue )
            throws ParameterValueException {

        Hashtable result;

        try {
            result = getHashtableInternal( name, null );
        } catch( ParameterMissingException e ) {
            result = defaultValue;
        }

        return result;
    }

    public Hashtable getHashtable( String name, String defaultValue )
            throws ParameterValueException {

        Hashtable result;

        try {
            result = getHashtableInternal( name, defaultValue );
        } catch( ParameterMissingException e ) {
            result = null;
        }

        return result;
    }

    public Vector getVector( String name )
            throws ParameterMissingException {

        return getVectorInternal( name, null );
    }

    public Vector getVector( String name, Vector defaultValue ) {
        Vector result;

        try {
            result = getVectorInternal( name, null );
        } catch( ParameterMissingException e ) {
            result = defaultValue;
        }

        return result;
    }

    public Vector getVector( String name, String defaultValue ) {
        Vector result;

        try {
            result = getVectorInternal( name, defaultValue );
        } catch( ParameterMissingException e ) {
            result = null;
        }

        return result;
    }

    public String[ ] getArray( String name )
            throws ParameterMissingException {

        return getArrayInternal( name, null );
    }

    public String[ ] getArray( String name, String[ ] defaultValue ) {
        String[ ] result;

        try {
            result = getArrayInternal( name, null );
        } catch( ParameterMissingException e ) {
            result = defaultValue;
        }

        return result;
    }

    public String[ ] getArray( String name, String defaultValue ) {
        String[ ] result;

        try {
            result = getArrayInternal( name, defaultValue );
        } catch( ParameterMissingException e ) {
            result = null;
        }

        return result;
    }




    //  Used by the internal methods below to allow a string default value
    //  for a non-string parameter.

    private String getParameterInternal( String name, String defaultValue ) {
        String result = stub.getParameter( name );

        if( result == null )
            result = defaultValue;

        return result;
    }

    //  Basic getURL method, with string default value.
    //  Used by getURL( String ), getURL( String, String ), getURL( String, URL ).

    private URL getURLInternal( String name, String defaultValue )
            throws ParameterMissingException, ParameterValueException {

        URL result;

        String parameterString = getParameterInternal( name, defaultValue );
        if( parameterString != null )
            try {
                result = new URL( base, parameterString );
            } catch( MalformedURLException e ) {
                throw new ParameterValueException( name, parameterString, "URL" );
            }
        else
            throw new ParameterMissingException( name );

        return result;
    }

    //  Basic getFileURL method, with string default value.
    //  Used by getFileURL( String ), getFileURL( String, String ), getFileURL( String, URL ).

    private URL getFileURLInternal( String name, String defaultValue )
            throws ParameterMissingException, ParameterValueException {

        URL result;

        String parameterString = getParameterInternal( name, defaultValue );
        if( parameterString != null )
            try {
                result = new URL( base, parameterString );
            } catch( MalformedURLException e ) {
                try {
                    result = new URL( base, "file:" + parameterString );
                } catch( MalformedURLException f ) {
                    throw new ParameterValueException( name, parameterString, "filename" );
                }
            }
        else
            throw new ParameterMissingException( name );

        return result;
    }

    private Hashtable getHashtableInternal( String name, String defaultValue )
            throws ParameterMissingException, ParameterValueException {

        Hashtable result;

        String parameterString = getParameterInternal( name, defaultValue );
        if( parameterString != null ) {
            result = new Hashtable( );

            if( parameterString.length( ) != 0 ) {
                StringBuffer pairBuffer = new StringBuffer( );
                StringBuffer keyBuffer = new StringBuffer( );
                int currentOffset = 0;

                do {
                    int nextOffset = indexOf( parameterString, ',', currentOffset, pairBuffer );
                    
                    if( nextOffset == -1 )
                        nextOffset = parameterString.length( );

                    String pairString = pairBuffer.toString( );
                    int equalsOffset = indexOf( pairString, '=', 0, keyBuffer );
                    
                    if( equalsOffset == -1 )
                        throw new ParameterValueException( name, pairString, "definition" );
                    else {
                        String key = keyBuffer.toString( );
                        String element = pairString.substring( equalsOffset + 1 );
                        result.put( key, element );
                    }

                    currentOffset = nextOffset + 1;
                    pairBuffer.setLength( 0 );
                    keyBuffer.setLength( 0 );
                } while( currentOffset <= parameterString.length( ));
            }
        } else
            throw new ParameterMissingException( name );

        return result;
    }

    // Basic getVector method, without call to trimToSize( ).
    // Used by getVectorInternal and getArrayInternal.

    private Vector getVectorInternalNoTrim( String name, String defaultValue )
            throws ParameterMissingException {

        Vector result;

        String parameterString = getParameterInternal( name, defaultValue );
        if( parameterString != null ) {
            result = new Vector( );

            if( parameterString.length( ) != 0 ) {
                StringBuffer elementBuffer = new StringBuffer( );
                int currentOffset = 0;

                do {
                    int nextOffset = indexOf( parameterString, ',', currentOffset, elementBuffer );

                    if( nextOffset == -1 )
                        nextOffset = parameterString.length( );

                    result.addElement( elementBuffer.toString( ));

                    currentOffset = nextOffset + 1;
                    elementBuffer.setLength( 0 );
                } while( currentOffset <= parameterString.length( ));
            }
        } else
            throw new ParameterMissingException( name );

        return result;
    }

    private Vector getVectorInternal( String name, String defaultValue )
            throws ParameterMissingException {

        Vector result = getVectorInternalNoTrim( name, defaultValue );
        result.trimToSize( );

        return result;
    }

    private String[ ] getArrayInternal( String name, String defaultValue )
            throws ParameterMissingException {

        Vector vector = getVectorInternal( name, defaultValue );
        String[ ] result = new String[ vector.size( ) ];
        vector.copyInto( result );

        return result;
    }




    // length parsing for the getLength methods:

    private static double unitConversion[ ] = {
         1.0, .394 };

    private static String unitName[ ] = {
        "in", "cm" };

    private double parseUnit( String unitString, int dpi, double defaultConversion )
            throws NumberFormatException {

        double  result = defaultConversion;
        boolean valid = false;

        if( unitString.length( ) > 0 ) {
            for( int i = 0; ! valid && i < unitName.length; ++ i )
                if( unitString.equalsIgnoreCase( unitName[ i ] )) {
                    result = unitConversion[ i ];
                    valid = true;
                }

            if( ! valid )
                throw new NumberFormatException( );
        }

        return result;
    }

    private int parseLength( String lengthString, int dpi, double defaultConversion )
            throws NumberFormatException {

        int unitsStart;
        for( unitsStart = lengthString.length( ) - 1; unitsStart >= 0; -- unitsStart ) {
            char c = lengthString.charAt( unitsStart );
            if( ! ( Character.isLowerCase( c ) || Character.isUpperCase( c )))
                break;
        }
        unitsStart ++;

        double conversion = parseUnit( lengthString.substring( unitsStart ), dpi, defaultConversion );
        double value = Double.valueOf( lengthString.substring( 0, unitsStart )).doubleValue( );
        return ( int )( value * conversion * dpi );
    }

    // Return the index of the next unquoted occurrence of delimiter, or -1
    // if the delimiter does not occur.  Occurrences of the delimiter which
    // are quoted with a backslash are ignored.  If the character at offset
    // is the delimiter and the character at offset - 1 is a backslash, the
    // backslash is (deliberately) *not* taken to be quoting the delimiter,
    // and the index returned is the original offset.
    //
    // The substring from the original offset to the resulting offset is
    // copied into the buffer, with any quoting backslashes removed.
    //
    // This routine is also used in the ParameterExpandStub
    
    static int indexOf( String string, char delimiter, int offset, StringBuffer buffer ) {

        int currentOffset = offset;
        int result = string.indexOf( delimiter, currentOffset );
        while( result != -1 && result != currentOffset && string.charAt( result - 1 ) == '\\' ) {
        
            buffer.append( string.substring( currentOffset, result - 1 ));
        
            currentOffset = result;
            result = string.indexOf( delimiter, currentOffset + 1 );
        }
        
        int finalOffset = result == -1 ? string.length( ) : result; 
        buffer.append( string.substring( currentOffset, finalOffset ));
        
        return result;
    }
}
